/****************************************************************************
 *
 *      $Id: network.c,v 1.1 2003/09/10 11:44:38 benjl Exp $
 *
 *      Description: Initialise the network stack and NFS library.
 *
 *      Author:      Ben Leslie
 *
 ****************************************************************************/

#include "network.h"

#include <assert.h>
#include <string.h>
#include <stdio.h>

#include <lwip/mem.h>
#include <lwip/memp.h>
#include <lwip/udp.h>
#include <lwip/pbuf.h>
#include <lwip/netif/etharp.h>
#include <lwip/netif/sosif.h>
#include <ixp_osal/IxOsal.h>
#include <nfs/nfs.h>

#include <mapping.h>
#include <ut_manager/ut.h>
#include <cspace/cspace.h>
#include <autoconf.h>

#define verbose 0
#include <sys/debug.h>
#include <sys/panic.h>

#ifndef CONFIG_SOS_NFS_DIR
#define CONFIG_SOS_NFS_DIR ""
#endif

struct cookie mnt_point = { { 0 } };

extern const seL4_BootInfo* _boot_info;

// Internal APIs, just direct publish from ixp_osal
extern uint32_t ixOsalOemInit(void);
extern void ixOsalOSServicesFinaliseInit(void);
extern void ixOsalOSServicesServiceInterrupt(seL4_Word irq);

// If the network_init is called from the root thread that handles
// interrupts then this flag needs to be set
#define NETWORK_INIT_FROM_ROOT_THREAD

#ifdef NETWORK_INIT_FROM_ROOT_THREAD
static int inNetworkInit = 0;
seL4_CPtr network_interrupt_endpoint = 0;
#endif

extern void abort(void);

void network_irq(seL4_Word irq) {
    /* skip if the network was not initialised */
    if(network_interrupt_endpoint != 0){
        ixOsalOSServicesServiceInterrupt(irq);
    }
}

#define US_TO_TICKS(us)     ((us) * 3333ULL / 50ULL)

extern void sos_usleep(int usecs) {
    // Need to convert to ticks
    UINT32 ticks = US_TO_TICKS(usecs);
    long start = ixOsalTimestampGet();
    long now = start;
    while (now - start < ticks) {
        seL4_Yield();
        now = ixOsalTimestampGet();
    }
#ifdef NETWORK_INIT_FROM_ROOT_THREAD
    if (inNetworkInit) {
        seL4_MessageInfo msgInfo;
        seL4_Word badge;
        while ((msgInfo = seL4_Poll(network_interrupt_endpoint, &badge)).label
                == seL4_Interrupt) {
            network_irq(seL4_GetMR(0));
        }
    }
#endif
}

void sos_wait_for_interrupt() {
    // If this is single threaded then need to poll for any interrupts
    // Otherwise just do a yield to help whatever thread is handling
    // interrupts get a chance to run
#ifdef NETWORK_INIT_FROM_ROOT_THREAD
    seL4_MessageInfo msgInfo;
    seL4_Word badge;
    while ((msgInfo = seL4_Poll(network_interrupt_endpoint, &badge)).label
            == seL4_Interrupt) {
        network_irq(seL4_GetMR(0));
    }
#else
    seL4_Yield();
#endif
}

void get_ip_option(struct ip_addr* ip, char* _str, const char* name){
    int oct[4];
    int i;
    char* str = _str;

    for(i = 0; i < 4; i++){
        oct[i] = 0;
        while(*str >= '0' && *str <= '9'){
            oct[i] = oct[i]*10 + *str - '0';
            str++;
        }

        if(oct[i] < 0 || oct[i] > 255 || *str != ((i == 3)? '\0': '.')){
            printf("Invalid configuration option for %s: \'%s\'\n", name, _str);
            panic("Network configuration failed\n");
        }
        str++;
    }
    IP4_ADDR(ip, oct[0], oct[1], oct[2], oct[3]);
    printf("%20s: %d.%d.%d.%d\n", name, oct[0], oct[1], oct[2], oct[3]);
}

void network_init(seL4_CPtr interrupt_ep) {
    struct ip_addr netmask, ipaddr, gw;
    struct netif *netif;
    struct pbuf *p;
    int count_out;

    printf("\nStarting %s\n\n", __FUNCTION__);
    get_ip_option(&netmask, CONFIG_SOS_NETMASK, "Network Mask"      );
    get_ip_option(&gw     , CONFIG_SOS_GATEWAY, "Gateway IP Address");
    get_ip_option(&ipaddr , CONFIG_SOS_IP     , "Local IP Address"  );
    printf("\n");

#ifdef NETWORK_INIT_FROM_ROOT_THREAD
    network_interrupt_endpoint = interrupt_ep;
    inNetworkInit = 1;
#endif

    // Initialise the nslu2 hardware
    ixOsalOemInit();

    /* Initialise lwIP */
    mem_init();
    memp_init();
    pbuf_init();
    netif_init();
    udp_init();
    etharp_init();

    /* Setup the network interface */
    netif = netif_add(&ipaddr, &netmask, &gw, sosIfInit, ip_input);
    assert(netif != NULL);
    netif_set_default(netif);

    // Generate an arp entry for our gateway
    // We should only need to do this once, but Linux seems to love ignoring
    // ARP queries (why??!), so we keep trying until we get a response
    p = etharp_query(netif, &netif->gw, NULL);
    assert(p != NULL);
    count_out = 50;
    do {
        err_t err = (*netif_default->linkoutput)(netif, p); // Direct output
        conditional_panic(err != 0, "update failed!");

        if(count_out-- == 0){
            printf("\nNetwork failed to respond to ARP query\n\n");
            abort();
        }

        sos_usleep(100000); // Wait a while for a reply
    } while (!etharp_entry_present(&netif->gw));
    pbuf_free(p);

    // Finish the initialisation of the nslu2 hardware
    ixOsalOSServicesFinaliseInit();

    /* Initialise NFS */
    if(strlen(CONFIG_SOS_NFS_DIR) > 0){
        int err;

        printf("\nMounting NFS\n");
        err = nfs_init(gw); 
        if(err){
            printf("Failed to initialise NFS\n");
        }else{

            /* Print out the exports on this server */
            mnt_get_export_list();

            if (mnt_mount(CONFIG_SOS_NFS_DIR, &mnt_point)){
                printf("Error mounting path '%s'!\n", CONFIG_SOS_NFS_DIR);
            }else{
                printf("\nSuccessfully mounted '%s'\n", CONFIG_SOS_NFS_DIR);
            }
        }
    }else{
        printf("\nBooting without NFS support\n");
    }

#ifdef NETWORK_INIT_FROM_ROOT_THREAD
    inNetworkInit = 0;
#endif
}

/****************************
 * Required by sync library *
 ****************************/
void* sync_new_ep(seL4_CPtr* ep_cap){
    seL4_Word aep_paddr;
    int err;

    aep_paddr = ut_alloc(seL4_EndpointBits);
    assert(aep_paddr != 0);

    err =  cspace_ut_retype_addr(aep_paddr, 
                                 seL4_AsyncEndpointObject,
                                 seL4_EndpointBits,
                                 cur_cspace,
                                 ep_cap);

    conditional_panic(err, "Failed to allocate memory for network endpoint");

    return (void*)!NULL;
}

void sync_free_ep(void* _ep){
    /* Leak */
}




/*******************************
 * Required by network library *
 *******************************/

int OsalMapDevice(seL4_Word paddr, seL4_Word vaddr){
    const seL4_DeviceRegion *device;
    seL4_ARM_Page page;
    int error;

    conditional_panic(_boot_info->deviceRegions == NULL, "No device regions in boot info");

    /* Find the device region */
    device = _boot_info->deviceRegions;
    while(device->basePaddr != paddr){
        if(++device == _boot_info->deviceRegions +  _boot_info->numDeviceRegions){
            return 0;
        }
    }

    /* map in a copy of all device frame caps */
    for(page = device->frames.start; page < device->frames.end; page++){
        seL4_CPtr frame_copy;

        frame_copy = cspace_copy_cap(cur_cspace, cur_cspace, page,  seL4_AllRights);
        assert(frame_copy);

        error = map_page(frame_copy, seL4_CapInitThreadPD, vaddr, 
                         seL4_AllRights, seL4_ARM_ParityEnabled);
        assert(!error);

        vaddr += (1 << device->frameSizeBits);
    }

    return page - device->frames.start;
}

int OsalUnmapDevice(seL4_Word paddr, seL4_Word vaddr){
    NOT_IMPLEMENTED();
    return 0; 
}

/* 
 * Creates an IRQ handler and Sets the async entpoint that
 * handles network IRQs
 */
seL4_IRQHandler OsalNewIRQ(int irq){
    seL4_CPtr p;
    int err;

    p = cspace_irq_control_get_cap(cur_cspace, seL4_CapIRQControl, irq);
    assert(p != CSPACE_NULL);

    err = seL4_IRQHandler_SetEndpoint(p, network_interrupt_endpoint);
    assert(!err || !"Could not bind to interrupt endpoint");

    return p;
}


void _logf(const char *msg, ...) {
    va_list alist;

    va_start(alist, msg);
    plogf(msg, alist);
    va_end(alist);
}


